Advanced Lane Finding Project

The goals / steps of this project are the following:

  • Compute the camera calibration matrix and distortion coefficients given a set of chessboard images.
  • Apply a distortion correction to raw images.
  • Use color transforms, gradients, etc., to create a thresholded binary image.
  • Apply a perspective transform to rectify binary image ("birds-eye view").
  • Detect lane pixels and fit to find the lane boundary.
  • Determine the curvature of the lane and vehicle position with respect to center.
  • Warp the detected lane boundaries back onto the original image.
  • Output visual display of the lane boundaries and numerical estimation of lane curvature and vehicle position.

In [1]:
import pickle
import cv2
import glob
import os
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
from os import listdir
from os.path import isfile, join
%matplotlib inline

Camera Calibration Matrix and Distortion Coefficient Calculations for a set of ChessBoard Images

In [2]:
nx = 9
ny = 6

objpoints = [] # 3-D points in Object Sapce
imgpoints = [] # 2-D points in Image Plane

images = glob.glob('camera_cal/calibration*.jpg')

print(images)

directory ='Undistorted_Images_Dir'
if not os.path.exists(directory):
    os.makedirs(directory)
['camera_cal\\calibration1.jpg', 'camera_cal\\calibration10.jpg', 'camera_cal\\calibration11.jpg', 'camera_cal\\calibration12.jpg', 'camera_cal\\calibration13.jpg', 'camera_cal\\calibration14.jpg', 'camera_cal\\calibration15.jpg', 'camera_cal\\calibration16.jpg', 'camera_cal\\calibration17.jpg', 'camera_cal\\calibration18.jpg', 'camera_cal\\calibration19.jpg', 'camera_cal\\calibration2.jpg', 'camera_cal\\calibration20.jpg', 'camera_cal\\calibration3.jpg', 'camera_cal\\calibration4.jpg', 'camera_cal\\calibration5.jpg', 'camera_cal\\calibration6.jpg', 'camera_cal\\calibration7.jpg', 'camera_cal\\calibration8.jpg', 'camera_cal\\calibration9.jpg']
In [3]:
def Calc_CamCalibMatrix(nx,ny,img,objpoints,imgpoints):
    objp = np.zeros((9*6,3), np.float32)
    objp[:,:2] = np.mgrid[0:9,0:6].T.reshape(-1,2)

    #Finding in mtx, dst
    #img = cv2.imread(filename)

    # Convert to grayscale
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    # Find the chessboard corners
    ret, corners = cv2.findChessboardCorners(gray, (nx, ny), None)

    # If found, draw corners
    if ret == True:
        imgpoints.append(corners)
        objpoints.append(objp)

        # Draw and display the corners
        cv2.drawChessboardCorners(img, (nx, ny), corners, ret)
        

    ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
    
    return ret, mtx, dist, rvecs, tvecs

Distortion Correction on Raw Images (Undistort Images)

In [4]:
def Undistort_Images(Image,CalibMatrix,Dist_Coeff):
    return cv2.undistort(Image,CalibMatrix,Dist_Coeff,None,CalibMatrix)
    

Distortion Correction Result on Single Image

In [6]:
nx = 9
ny = 6

objpoints = [] # 3-D points in Object Sapce
imgpoints = [] # 2-D points in Image Plane
objp = np.zeros((9*6,3), np.float32)
objp[:,:2] = np.mgrid[0:9,0:6].T.reshape(-1,2)

#Finding in mtx, dst
img = cv2.imread('camera_cal/calibration3.jpg')

# Convert to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# Find the chessboard corners
ret, corners = cv2.findChessboardCorners(gray, (nx, ny), None)

# If found, draw corners
if ret == True:
    imgpoints.append(corners)
    objpoints.append(objp)

    # Draw and display the corners
    cv2.drawChessboardCorners(img, (nx, ny), corners, ret)

ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)

undistorted = Undistort_Images(img, mtx, dist)

f, (ax1, ax2) = plt.subplots(1, 2, figsize=(24, 9))
f.tight_layout()
ax1.imshow(img)
ax1.set_title('Original Image', fontsize=50)
ax2.imshow(undistorted)
ax2.set_title('Undistorted Image', fontsize=50)
plt.subplots_adjust(left=0., right=1, top=0.9, bottom=0.)

Display Undistorted ChessBoard Images

In [17]:
def Disp_UnDistortedImages(images): 
    for filename in images:
        if(filename=='camera_cal\\calibration2.jpg' or filename=='test_images\\test1.jpg' or filename=='test_images\\straight_lines1.jpg'):
            img = cv2.imread(filename)
            img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
            ret, mtx, dist, rvecs, tvecs = Calc_CamCalibMatrix(nx,ny,img,objpoints,imgpoints)
            Undistorted_Image = Undistort_Images(img,mtx,dist)
            filenamesplit = filename.split("\\")
            Undistorted_Image_Save = cv2.cvtColor(Undistorted_Image, cv2.COLOR_BGR2RGB)
            cv2.imwrite(directory +'\\Undistorted_'+filenamesplit[1],Undistorted_Image_Save)
            f, (ax1, ax2) = plt.subplots(1, 2, figsize=(24, 9))
            f.tight_layout()
            ax1.imshow(img)
            ax1.set_title('Original Image', fontsize=50)
            ax2.imshow(Undistorted_Image)
            ax2.set_title('Undistorted Image', fontsize=50)
            plt.subplots_adjust(left=0., right=1, top=0.9, bottom=0.)
        
In [18]:
Disp_UnDistortedImages(images)

Display Undistorted Test Images

In [19]:
testimages = glob.glob('test_images/test*.jpg')

print(testimages)
['test_images\\test1.jpg', 'test_images\\test2.jpg', 'test_images\\test3.jpg', 'test_images\\test4.jpg', 'test_images\\test5.jpg', 'test_images\\test6.jpg']
In [20]:
Disp_UnDistortedImages(testimages)
In [21]:
strline_images = glob.glob('test_images/straight_lines*.jpg')

print(strline_images)
['test_images\\straight_lines1.jpg', 'test_images\\straight_lines2.jpg']
In [22]:
Disp_UnDistortedImages(strline_images)

Create Threshold Binary Images

In [23]:
def abs_sobel_thresh(img, orient='x', sobel_kernel=3, thresh=(0, 255)):
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
    scaled_sobel = None
    
    # Sobel x
    if orient == 'x':
        sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0,ksize=sobel_kernel) # Take the derivative in x
        abs_sobelx = np.absolute(sobelx) # Absolute x derivative to accentuate lines away from horizontal
        scaled_sobel = np.uint8(255*abs_sobelx/np.max(abs_sobelx))
        
    # Sobel y
    else:
        sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=sobel_kernel) # Take the derivative in y
        abs_sobely = np.absolute(sobely) # Absolute x derivative to accentuate lines away from horizontal
        scaled_sobel = np.uint8(255*abs_sobely/np.max(abs_sobely))

    # Threshold x gradient
    thresh_min = thresh[0]
    thresh_max = thresh[1]
    grad_binary = np.zeros_like(scaled_sobel)
    grad_binary[(scaled_sobel >= thresh_min) & (scaled_sobel <= thresh_max)] = 1
    
    return grad_binary

def mag_thresh(img, sobel_kernel=3, mag_thresh=(0, 255)):
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
    sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0,ksize=sobel_kernel)
    sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1,ksize=sobel_kernel)
    magnitude = np.sqrt(np.square(sobelx)+np.square(sobely))
    abs_magnitude = np.absolute(magnitude)
    scaled_magnitude = np.uint8(255*abs_magnitude/np.max(abs_magnitude))
    mag_binary = np.zeros_like(scaled_magnitude)
    mag_binary[(scaled_magnitude >= mag_thresh[0]) & (scaled_magnitude <= mag_thresh[1])] = 1
    
    return mag_binary

def dir_threshold(img, sobel_kernel=3, thresh=(0, np.pi/2)):
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
    sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0,ksize=sobel_kernel)
    sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1,ksize=sobel_kernel)
    abs_sobelx = np.absolute(sobelx)
    abs_sobely = np.absolute(sobely)
    arctan = np.arctan2(abs_sobely, abs_sobelx)
    dir_binary = np.zeros_like(arctan)
    dir_binary[(arctan >= thresh[0]) & (arctan <= thresh[1])] = 1
    
    return dir_binary


def combined_s_gradient_thresholds(img,visulaize_perstranfrom_flag=False,show=False):

    # Choose a Sobel kernel size
    ksize = 3 # Choose a larger odd number to smooth gradient measurements

    # Apply each of the thresholding functions
    gradx = abs_sobel_thresh(img, orient='x', sobel_kernel=ksize, thresh=(20, 100))
    grady = abs_sobel_thresh(img, orient='y', sobel_kernel=ksize, thresh=(20, 100))
    mag_binary = mag_thresh(img, sobel_kernel=ksize, mag_thresh=(20, 100))
    dir_binary = dir_threshold(img, sobel_kernel=ksize, thresh=(0.7, 1.4))
    
    combined = np.zeros_like(dir_binary)
    combined[((gradx == 1) & (grady == 1)) | ((mag_binary == 1) & (dir_binary == 1))] = 1
    
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    hls = cv2.cvtColor(img, cv2.COLOR_RGB2HLS)
    s_channel = hls[:,:,2]

    # Threshold color channel
    s_thresh_min = 150
    s_thresh_max = 255
    s_binary = np.zeros_like(s_channel)
    s_binary[(s_channel >= s_thresh_min) & (s_channel <= s_thresh_max)] = 1

    # Combine the two binary thresholds
    combined_binary = np.zeros_like(combined)
    
    combined_binary[(s_binary == 1) | (combined == 1)] = 1
    
    #filenamesplit = filename.split("\\")
    
    #directory = 'ComboThreshGrad_ImgList'
    #if not os.path.exists(directory):
        #os.makedirs(directory)
    #gray_binary = cv2.cvtColor(combined_binary, cv2.COLOR_RGB2GRAY)
    #cv2.imwrite(directory +'\\Combined_BinaryImages_'+filenamesplit[1],gray_binary)
    
    if show == True:
        if(visulaize_perstranfrom_flag==False):
            f, (ax1, ax2, ax3, ax4) = plt.subplots(1, 4, figsize=(20,10))
            ax1.set_title('Actual Undistorted image')
            ax1.imshow(img)
            ax2.set_title('Combined gradx,grady,magnitude,direction')
            ax2.imshow(combined, cmap='gray')
            ax3.set_title('Color thresholding')
            ax3.imshow(s_binary, cmap='gray')
            ax4.set_title('Combined all')
            ax4.imshow(combined_binary, cmap='gray')
        
    return combined_binary
In [24]:
def Display_Threshold_Binary_Images(testimages):
    for filename in testimages:
        if(filename=='test_images\\test1.jpg'):
            img = cv2.imread(filename)
            ret, mtx, dist, rvecs, tvecs = Calc_CamCalibMatrix(nx,ny,img,objpoints,imgpoints)
            Undistorted_Image = Undistort_Images(img,mtx,dist)
            #combined_binary_image = combined_s_gradient_thresholds(img,filename,False, True)
            combined_binary_image = combined_s_gradient_thresholds(Undistorted_Image,False, True)
    
In [25]:
Display_Threshold_Binary_Images(testimages)

Perspective Transform step

In [26]:
# Perspective Transform Function
def Perpective_Transform_Image(combined_binary_image, nx, ny): 
    offset = 100 # offset for dst points
    
    # Grab the image shape
    img_size = (combined_binary_image.shape[1], combined_binary_image.shape[0])
    
    leftupperpoint  = [568,470]
    rightupperpoint = [717,470]
    leftlowerpoint  = [260,680]
    rightlowerpoint = [1043,680]

    src = np.float32([leftupperpoint, leftlowerpoint, rightupperpoint, rightlowerpoint])
    dst = np.float32([[200,0], [200,680], [1000,0], [1000,680]])

    # Given src and dst points, calculate the perspective transform matrix
    PerspTrnsfrm_Matrix = cv2.getPerspectiveTransform(src, dst)

    # Warp the image using OpenCV warpPerspective()
    warped = cv2.warpPerspective(combined_binary_image, PerspTrnsfrm_Matrix, img_size, flags=cv2.INTER_NEAREST)
    
    return warped, PerspTrnsfrm_Matrix
In [27]:
def Display_PerspecTransfrmed_Image(testimages,visulaize_perstranfrom_flag = True):
    #directory = 'Warped_Images'
    #if not os.path.exists(directory):
            #os.makedirs(directory)
    for filename in testimages:
        if(filename=='test_images\\test1.jpg'):
            img = cv2.imread(filename)
            ret, mtx, dist, rvecs, tvecs = Calc_CamCalibMatrix(nx,ny,img,objpoints,imgpoints)
            Undistorted_Image = Undistort_Images(img,mtx,dist)
            combined_binary_image = combined_s_gradient_thresholds(Undistorted_Image,visulaize_perstranfrom_flag, True)
            warped_img, PerspTrnsfrm_Matrix = Perpective_Transform_Image(combined_binary_image, nx, ny)    
            #gray_binary = cv2.cvtColor(combined_binary, cv2.COLOR_RGB2GRAY)
            #filenamesplit = filename.split('\\')
            #cv2.imwrite(directory +'\\WarpedImages_'+filenamesplit[1],warped_img)
            f, (ax1, ax2) = plt.subplots(1, 2, figsize=(24, 9))
            f.tight_layout()
            ax1.imshow(combined_binary_image, cmap='gray')
            ax1.set_title('Binary Threshold Image', fontsize=50)
            ax2.imshow(warped_img,cmap='gray')
            ax2.set_title('Warped Image', fontsize=50)
            plt.subplots_adjust(left=0., right=1, top=0.9, bottom=0.)
In [28]:
Display_PerspecTransfrmed_Image(testimages,True)

Detecting Lane Lines

In [29]:
def Detect_lines(binary_warped, nwindows = 9, margin = 100, minpix = 50):
    # Assuming you have created a warped binary image called "binary_warped"
    # Take a histogram of the bottom half of the image
    histogram = np.sum(binary_warped[int(binary_warped.shape[0]/2):,:], axis=0)
    
    # Find the peak of the left and right halves of the histogram
    # These will be the starting point for the left and right lines
    midpoint = np.int(histogram.shape[0]/2)
    leftx_base = np.argmax(histogram[:midpoint])
    rightx_base = np.argmax(histogram[midpoint:]) + midpoint
    
    # Set height of windows
    window_height = np.int(binary_warped.shape[0]/nwindows)
    # Identify the x and y positions of all nonzero pixels in the image
    nonzero = binary_warped.nonzero()
    nonzeroy = np.array(nonzero[0])
    nonzerox = np.array(nonzero[1])
    # Current positions to be updated for each window
    leftx_current = leftx_base
    rightx_current = rightx_base
    
    # Create empty lists to receive left and right lane pixel indices
    left_lane_inds = []
    right_lane_inds = []
    
    # Create an image to draw on and an image to show the selection window
    out_img = np.dstack((binary_warped, binary_warped, binary_warped))*255

    # Step through the windows one by one
    for window in range(nwindows):
        # Identify window boundaries in x and y (and right and left)
        win_y_low = binary_warped.shape[0] - (window+1)*window_height
        win_y_high = binary_warped.shape[0] - window*window_height
        win_xleft_low = leftx_current - margin
        win_xleft_high = leftx_current + margin
        win_xright_low = rightx_current - margin
        win_xright_high = rightx_current + margin
        # Draw the windows on the visualization image
        cv2.rectangle(out_img,(win_xleft_low,win_y_low),(win_xleft_high,win_y_high),
        (0,255,0), 2) 
        cv2.rectangle(out_img,(win_xright_low,win_y_low),(win_xright_high,win_y_high),
        (0,255,0), 2) 
        # Identify the nonzero pixels in x and y within the window
        good_left_inds = ((nonzeroy >= win_y_low) & (nonzeroy < win_y_high) & 
        (nonzerox >= win_xleft_low) &  (nonzerox < win_xleft_high)).nonzero()[0]
        good_right_inds = ((nonzeroy >= win_y_low) & (nonzeroy < win_y_high) & 
        (nonzerox >= win_xright_low) &  (nonzerox < win_xright_high)).nonzero()[0]
        # Append these indices to the lists
        left_lane_inds.append(good_left_inds)
        right_lane_inds.append(good_right_inds)
        # If you found > minpix pixels, recenter next window on their mean position
        if len(good_left_inds) > minpix:
            leftx_current = np.int(np.mean(nonzerox[good_left_inds]))
        if len(good_right_inds) > minpix:        
            rightx_current = np.int(np.mean(nonzerox[good_right_inds]))

    # Concatenate the arrays of indices
    left_lane_inds = np.concatenate(left_lane_inds)
    right_lane_inds = np.concatenate(right_lane_inds)

    # Extract left and right line pixel positions
    leftx = nonzerox[left_lane_inds]
    lefty = nonzeroy[left_lane_inds] 
    rightx = nonzerox[right_lane_inds]
    righty = nonzeroy[right_lane_inds] 

    # Fit a second order polynomial to each
    left_fit = np.polyfit(lefty, leftx, 2)
    right_fit = np.polyfit(righty, rightx, 2)
    
    return left_fit, right_fit,left_lane_inds, right_lane_inds, nonzerox, nonzeroy

def Detect_lines_beyond(left_fit, right_fit, binary_warped):
    
    nonzero = binary_warped.nonzero()
    nonzeroy = np.array(nonzero[0])
    nonzerox = np.array(nonzero[1])
    margin = 50
    left_lane_inds = ((nonzerox > (left_fit[0]*(nonzeroy**2) + left_fit[1]*nonzeroy + left_fit[2] - margin)) 
                      & (nonzerox < (left_fit[0]*(nonzeroy**2) + left_fit[1]*nonzeroy + left_fit[2] + margin))) 
    right_lane_inds = ((nonzerox > (right_fit[0]*(nonzeroy**2) + right_fit[1]*nonzeroy + right_fit[2] - margin)) 
                       & (nonzerox < (right_fit[0]*(nonzeroy**2) + right_fit[1]*nonzeroy + right_fit[2] + margin)))  

    # Again, extract left and right line pixel positions
    leftx = nonzerox[left_lane_inds]
    lefty = nonzeroy[left_lane_inds] 
    rightx = nonzerox[right_lane_inds]
    righty = nonzeroy[right_lane_inds]
    
    # Fit a second order polynomial to each
    if len(leftx) == 0:
        left_fit_new =[]
    else:
        left_fit_new = np.polyfit(lefty, leftx, 2)
    
    if len(rightx) == 0:
        right_fit_new =[]
    else:
        right_fit_new = np.polyfit(righty, rightx, 2)
     
    return left_fit_new, right_fit_new

Displaying Detected Lane Lines

In [31]:
def Display_DetectedLanes(f, ax,testimages,visulaize_perstranfrom_flag = True,margin = 100):
    
    i=0
    j=0

    for filename in testimages:
        #if(filename=='test_images\\test1.jpg'):
        img = cv2.imread(filename)
        ret, mtx, dist, rvecs, tvecs = Calc_CamCalibMatrix(nx,ny,img,objpoints,imgpoints)
        Undistorted_Image = Undistort_Images(img,mtx,dist)
        combined_binary_image = combined_s_gradient_thresholds(Undistorted_Image,visulaize_perstranfrom_flag, True)
        warped_img, PerspTrnsfrm_Matrix = Perpective_Transform_Image(combined_binary_image, nx, ny) 
        left_fit, right_fit,left_lane_inds, right_lane_inds, nonzerox, nonzeroy = Detect_lines(warped_img) 
    
        # Generate x and y values for plotting
        ploty = np.linspace(0, warped_img.shape[0]-1, warped_img.shape[0] )
        left_fitx = left_fit[0]*ploty**2 + left_fit[1]*ploty + left_fit[2]
        right_fitx = right_fit[0]*ploty**2 + right_fit[1]*ploty + right_fit[2]
    
        # Create an image to draw on and an image to show the selection window
        out_img = np.dstack((warped_img, warped_img, warped_img))*255
        window_img = np.zeros_like(out_img)
        # Color in left and right line pixels
        out_img[nonzeroy[left_lane_inds], nonzerox[left_lane_inds]] = [255, 0, 0]
        out_img[nonzeroy[right_lane_inds], nonzerox[right_lane_inds]] = [0, 0, 255]

        # Generate a polygon to illustrate the search window area
        # And recast the x and y points into usable format for cv2.fillPoly()
        left_line_window1 = np.array([np.transpose(np.vstack([left_fitx-margin, ploty]))])
        left_line_window2 = np.array([np.flipud(np.transpose(np.vstack([left_fitx+margin, 
                                        ploty])))])
        left_line_pts = np.hstack((left_line_window1, left_line_window2))
        right_line_window1 = np.array([np.transpose(np.vstack([right_fitx-margin, ploty]))])
        right_line_window2 = np.array([np.flipud(np.transpose(np.vstack([right_fitx+margin, 
                                        ploty])))])
        right_line_pts = np.hstack((right_line_window1, right_line_window2))

        # Draw the lane onto the warped blank image
        cv2.fillPoly(window_img, np.int_([left_line_pts]), (0,255, 0))
        cv2.fillPoly(window_img, np.int_([right_line_pts]), (0,255, 0))
        result = cv2.addWeighted(out_img, 1, window_img, 0.3, 0)
        
        ax[i,j].set_title('Detected Lanes')
        
        # rseultreshaped = np.reshape(result,(result.shape[1],result.shape[0],result.shape[2]))

        ax[i,j].imshow(result) 
        
        #print(rseultreshaped.shape)
        
        #indexlist_leftlane =[]
        
        #indexlist_rightlane =[]
        
        #for k in range(len(left_fitx)):
            #col = k+1
            #indexlist_leftlane.append("A"+ str(col))
            #indexlist_rightlane.append("A"+ str(col))
            
        #left_fitx_series = pd.DataFrame(data=left_fitx, dtype='int',index=indexlist_leftlane,columns =['Coords'])
        
        #right_fitx_series =  pd.DataFrame(data=right_fitx, dtype='int',index=indexlist_rightlane,columns=['Coords'])
        
        #left_lane = left_fitx_series.plot(ax=ax[i,j], color='yellow')
        
        #right_lane = right_fitx_series.plot(ax=ax[i,j],color='yellow')
        
        ax[i,j].plot(left_fitx, ploty, color='yellow')
        ax[i,j].plot(right_fitx, ploty, color='yellow')
        ax[i,j].set_xlim(0, 1280)
        ax[i,j].set_ylim(720, 0)
        
        if(j==1):
            j=0
            i=i+1
        elif(j==0):
            j =j+1
            
In [32]:
f, ax = plt.subplots(int(len(testimages)/2), int(len(testimages)/3), figsize=(11,11))


        
Display_DetectedLanes(f, ax,testimages,True, margin = 100)

Radius of Curvature Calculation

In [33]:
# Radius of Curvature Calculation
def radius_curvature_vals(binary_warped, left_fit, right_fit):
    
    ploty = np.linspace(0, binary_warped.shape[0]-1, binary_warped.shape[0] )
    left_fitx = left_fit[0]*ploty**2 + left_fit[1]*ploty + left_fit[2]
    right_fitx = right_fit[0]*ploty**2 + right_fit[1]*ploty + right_fit[2]
    
    # Define conversions in x and y from pixels space to meters
    ym_per_pix = 30/720 # meters per pixel in y dimension
    xm_per_pix = 3.7/700 # meters per pixel in x dimension
    y_eval = np.max(ploty)
    
    # Fit new polynomials to x,y in world space
    left_fit_cr = np.polyfit(ploty*ym_per_pix, left_fitx*xm_per_pix, 2)
    right_fit_cr = np.polyfit(ploty*ym_per_pix, right_fitx*xm_per_pix, 2)
    
    # Calculate the new radii of curvature
    left_curvature =  ((1 + (2*left_fit_cr[0] *y_eval*ym_per_pix + left_fit_cr[1])**2) **1.5) / np.absolute(2*left_fit_cr[0])
    right_curvature = ((1 + (2*right_fit_cr[0]*y_eval*ym_per_pix + right_fit_cr[1])**2)**1.5) / np.absolute(2*right_fit_cr[0])
    
    # Calculate vehicle center
    #left_lane and right lane bottom in pixels
    left_lane_bottom = (left_fit[0]*y_eval)**2 + left_fit[0]*y_eval + left_fit[2]
    right_lane_bottom = (right_fit[0]*y_eval)**2 + right_fit[0]*y_eval + right_fit[2]
    
    # Lane center as mid of left and right lane bottom                        
    lane_center = (left_lane_bottom + right_lane_bottom)/2.
    center_image = 640
    #value in meters
    center = (lane_center - center_image)*xm_per_pix 
    position = "left" if center < 0 else "right"
    center = "Vehicle is {:.2f}m {}".format(center, position)

    return left_curvature, right_curvature, center
In [34]:
def Visualize_Lanes_on_Image(testimages,visulaize_perstranfrom_flag = True,display_results=True):
    for filename in testimages:
        if(filename=='test_images\\test1.jpg'):
            img = cv2.imread(filename)
            img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
            ret, mtx, dist, rvecs, tvecs = Calc_CamCalibMatrix(nx,ny,img,objpoints,imgpoints)
            Undistorted_Image = Undistort_Images(img,mtx,dist)
            combined_binary_image = combined_s_gradient_thresholds(Undistorted_Image,visulaize_perstranfrom_flag, True)
            warped_img, PerspTrnsfrm_Matrix = Perpective_Transform_Image(combined_binary_image, nx, ny) 
            left_fit, right_fit,left_lane_inds, right_lane_inds, nonzerox, nonzeroy = Detect_lines(warped_img) 
            left_curvature, right_curvature, center = radius_curvature_vals(warped_img, left_fit, right_fit)
        
            ploty = np.linspace(0, warped_img.shape[0]-1, warped_img.shape[0] )
            left_fitx = left_fit[0]*ploty**2 + left_fit[1]*ploty + left_fit[2]
            right_fitx = right_fit[0]*ploty**2 + right_fit[1]*ploty + right_fit[2]

            # Create an image to draw the lines on
            warp_zero = np.zeros_like(warped_img).astype(np.uint8)
            color_warp = np.dstack((warp_zero, warp_zero, warp_zero))

            # Recast the x and y points into usable format for cv2.fillPoly()
            pts_left = np.array([np.transpose(np.vstack([left_fitx, ploty]))])
            pts_right = np.array([np.flipud(np.transpose(np.vstack([right_fitx, ploty])))])
            pts = np.hstack((pts_left, pts_right))

            # Draw the lane onto the warped blank image
            cv2.fillPoly(color_warp, np.int_([pts]), (0,255, 0))
        
            PerspTrnsfrm_Matrix_inv = np.linalg.inv(PerspTrnsfrm_Matrix)
            # Warp the blank back to original image space using inverse perspective matrix (Minv)
            newwarp = cv2.warpPerspective(color_warp, PerspTrnsfrm_Matrix_inv, (img.shape[1], img.shape[0])) 
            # Combine the result with the original image
            result = cv2.addWeighted(img, 1, newwarp, 0.3, 0)
    
            cv2.putText(result, 'Left curvature: {:.0f} m'.format(left_curvature), (50, 50), cv2.FONT_HERSHEY_DUPLEX, 1, (255, 255, 255), 2)
            cv2.putText(result, 'Right curvature: {:.0f} m'.format(right_curvature), (50, 100), cv2.FONT_HERSHEY_DUPLEX, 1, (255, 255, 255), 2)
            cv2.putText(result, '{}'.format(center), (50, 150), cv2.FONT_HERSHEY_DUPLEX, 1, (255, 255, 255), 2)
            if(display_results == True):
                fig, ax = plt.subplots(figsize=(20, 10))
                ax.imshow(result)
            
        
In [35]:
Visualize_Lanes_on_Image(testimages,True,True)
In [36]:
def is_lane_valid(left_fit, right_fit):
    
    #Check if left and right fit returned a value
    if len(left_fit) ==0 or len(right_fit) == 0:
        status = False

    else:
        #Check distance b/w lines
        ploty = np.linspace(0, 20, num=10 )
        left_fitx = left_fit[0]*ploty**2 + left_fit[1]*ploty + left_fit[2]
        right_fitx = right_fit[0]*ploty**2 + right_fit[1]*ploty + right_fit[2]
        delta_lines = np.mean(right_fitx - left_fitx)
        if delta_lines >= 150 and delta_lines <=430: 
            status = True
        else:
            status = False
        
        # Calculate slope of left and right lanes at midpoint of y (i.e. 360)
        left = 2*left_fit[0]*360+left_fit[1]
        right = 2*right_fit[0]*360+right_fit[1]
        delta_slope_mid =  np.abs(left-right)
        
        #Check if lines are parallel at the middle
        if delta_slope_mid <= 0.1:
            status = True
        else:
            status = False
            
    return status
In [37]:
# Lane Class Definition
class Lane():
    def __init__(self):
        self.last_left = None
        self.last_right = None
        self.left_fit = None
        self.right_fit = None
        self.counter = 0
        self.reset_counter = 0
        
lane = Lane()
In [38]:
def detect_lanes(img):
    ret, mtx, dist, rvecs, tvecs = Calc_CamCalibMatrix(nx,ny,img,objpoints,imgpoints)
    Undistorted_Image = Undistort_Images(img,mtx,dist)
    combined_binary_image = combined_s_gradient_thresholds(Undistorted_Image,True, True)
    warped_img, PerspTrnsfrm_Matrix = Perpective_Transform_Image(combined_binary_image, nx, ny) 
    
    if lane.counter == 0:
        lane.left_fit, lane.right_fit,left_lane_inds, right_lane_inds, nonzerox, nonzeroy = Detect_lines(warped_img)
    else:
        lane.left_fit, lane.right_fit  = Detect_lines_beyond(lane.left_fit, lane.right_fit, warped_img)
    
    # Sanity check
    status = is_lane_valid(lane.left_fit, lane.right_fit)
    
    if status == True:        
        lane.last_left, lane.last_right = lane.left_fit, lane.right_fit        
        lane.counter += 1
        lane.reset_counter = 0
    else:   
        # Reset
        if lane.reset_counter > 4:
            lane.left_fit, lane.right_fit,left_lane_inds, right_lane_inds, nonzerox, nonzeroy = Detect_lines(warped_img)
            lane.reset_counter = 0
        else:
            lane.left_fit, lane.right_fit = lane.last_left, lane.last_right
            
        lane.reset_counter += 1
    
    return warped_img, lane.left_fit, lane.right_fit, PerspTrnsfrm_Matrix
In [39]:
def final_pipeline_function(testimages, display_results=False):
    for filename in testimages:
         if(filename=='test_images\\test1.jpg'):
             img = cv2.imread(filename)
             img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)  
             warped_img, left_fit, right_fit, PerspTrnsfrm_Matrix = detect_lanes(img)
             left_curvature, right_curvature, center = radius_curvature_vals(warped_img, left_fit, right_fit)
            
             ploty = np.linspace(0, warped_img.shape[0]-1, warped_img.shape[0] )
             left_fitx = left_fit[0]*ploty**2 + left_fit[1]*ploty + left_fit[2]
             right_fitx = right_fit[0]*ploty**2 + right_fit[1]*ploty + right_fit[2]

             # Create an image to draw the lines on
             warp_zero = np.zeros_like(warped_img).astype(np.uint8)
             color_warp = np.dstack((warp_zero, warp_zero, warp_zero))

             # Recast the x and y points into usable format for cv2.fillPoly()
             pts_left = np.array([np.transpose(np.vstack([left_fitx, ploty]))])
             pts_right = np.array([np.flipud(np.transpose(np.vstack([right_fitx, ploty])))])
             pts = np.hstack((pts_left, pts_right))

             # Draw the lane onto the warped blank image
             cv2.fillPoly(color_warp, np.int_([pts]), (0,255, 0))
        
             PerspTrnsfrm_Matrix_inv = np.linalg.inv(PerspTrnsfrm_Matrix)
             # Warp the blank back to original image space using inverse perspective matrix (Minv)
             newwarp = cv2.warpPerspective(color_warp, PerspTrnsfrm_Matrix_inv, (img.shape[1], img.shape[0])) 
             # Combine the result with the original image
             result = cv2.addWeighted(img, 1, newwarp, 0.3, 0)
    
             cv2.putText(result, 'Left curvature: {:.0f} m'.format(left_curvature), (50, 50), cv2.FONT_HERSHEY_DUPLEX, 1, (255, 255, 255), 2)
             cv2.putText(result, 'Right curvature: {:.0f} m'.format(right_curvature), (50, 100), cv2.FONT_HERSHEY_DUPLEX, 1, (255, 255, 255), 2)
             cv2.putText(result, '{}'.format(center), (50, 150), cv2.FONT_HERSHEY_DUPLEX, 1, (255, 255, 255), 2)
             if(display_results == True):
                 fig, ax = plt.subplots(figsize=(20, 10))
                 ax.imshow(result)
            
            
    
    
In [40]:
final_pipeline_function(testimages,True)

Advance Lane Detection on Videos

In [41]:
from moviepy.editor import VideoFileClip
from IPython.display import HTML
In [42]:
def final_pipeline_function_video(frame, display_results=False):
    
    #img = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)  
    warped_img, left_fit, right_fit, PerspTrnsfrm_Matrix = detect_lanes(frame)
    
    
    left_curvature, right_curvature, center = radius_curvature_vals(warped_img, left_fit, right_fit)
            
    ploty = np.linspace(0, warped_img.shape[0]-1, warped_img.shape[0] )
    
    
    left_fitx = left_fit[0]*ploty**2 + left_fit[1]*ploty + left_fit[2]
    right_fitx = right_fit[0]*ploty**2 + right_fit[1]*ploty + right_fit[2]

    # Create an image to draw the lines on
    warp_zero = np.zeros_like(warped_img).astype(np.uint8)
    color_warp = np.dstack((warp_zero, warp_zero, warp_zero))

    # Recast the x and y points into usable format for cv2.fillPoly()
    pts_left = np.array([np.transpose(np.vstack([left_fitx, ploty]))])
    pts_right = np.array([np.flipud(np.transpose(np.vstack([right_fitx, ploty])))])
    pts = np.hstack((pts_left, pts_right))

    # Draw the lane onto the warped blank image
    cv2.fillPoly(color_warp, np.int_([pts]), (0,255, 0))
        
    PerspTrnsfrm_Matrix_inv = np.linalg.inv(PerspTrnsfrm_Matrix)
    # Warp the blank back to original image space using inverse perspective matrix (Minv)
    newwarp = cv2.warpPerspective(color_warp, PerspTrnsfrm_Matrix_inv, (frame.shape[1], frame.shape[0])) 
    # Combine the result with the original image
    result = cv2.addWeighted(frame, 1, newwarp, 0.3, 0)
    
    cv2.putText(result, 'Left curvature: {:.0f} m'.format(left_curvature), (50, 50), cv2.FONT_HERSHEY_DUPLEX, 1, (255, 255, 255), 2)
    cv2.putText(result, 'Right curvature: {:.0f} m'.format(right_curvature), (50, 100), cv2.FONT_HERSHEY_DUPLEX, 1, (255, 255, 255), 2)
    cv2.putText(result, '{}'.format(center), (50, 150), cv2.FONT_HERSHEY_DUPLEX, 1, (255, 255, 255), 2)
    if(display_results == True):
        fig, ax = plt.subplots(figsize=(20, 10))
        ax.imshow(result)
        
    return  result 
            
In [43]:
lane = Lane()
def process_image(frame):
    return final_pipeline_function_video(frame,False)
In [37]:
white_output = 'project_video_output.mp4'
clip1 = VideoFileClip("project_video.mp4")
white_clip = clip1.fl_image(process_image)
%time white_clip.write_videofile(white_output, audio=False)
[MoviePy] >>>> Building video project_video_output.mp4
[MoviePy] Writing video project_video_output.mp4
100%|███████████████████████████████████████████████████████████████████████████▉| 1260/1261 [1:04:58<00:03,  3.09s/it]
[MoviePy] Done.
[MoviePy] >>>> Video ready: project_video_output.mp4 

Wall time: 1h 5min 3s
In [38]:
challenge_video_output = 'challenge_video_output.mp4'
clip1_challenge_video = VideoFileClip("challenge_video.mp4")
challenge_video_clip = clip1_challenge_video.fl_image(process_image)
%time challenge_video_clip.write_videofile(challenge_video_output, audio=False)
[MoviePy] >>>> Building video challenge_video_output.mp4
[MoviePy] Writing video challenge_video_output.mp4
100%|████████████████████████████████████████████████████████████████████████████████| 485/485 [30:35<00:00,  3.78s/it]
[MoviePy] Done.
[MoviePy] >>>> Video ready: challenge_video_output.mp4 

Wall time: 30min 41s
In [40]:
harder_challenge_video_output = 'harder_challenge_video_output.mp4'
clip1_harder_challenge_video = VideoFileClip("harder_challenge_video.mp4").subclip(0,15)
harder_challenge_video_clip = clip1_harder_challenge_video.fl_image(process_image)
%time harder_challenge_video_clip.write_videofile(harder_challenge_video_output, audio=False)
[MoviePy] >>>> Building video harder_challenge_video_output.mp4
[MoviePy] Writing video harder_challenge_video_output.mp4
  0%|                                                                                          | 0/376 [00:00<?, ?it/s]
  0%|▏                                                                                 | 1/376 [00:04<28:39,  4.59s/it]
  1%|▍                                                                                 | 2/376 [00:07<22:35,  3.62s/it]
  1%|▋                                                                                 | 3/376 [00:11<24:17,  3.91s/it]
  1%|▊                                                                                 | 4/376 [00:14<22:15,  3.59s/it]
  1%|█                                                                                 | 5/376 [00:19<24:26,  3.95s/it]
  2%|█▎                                                                                | 6/376 [00:24<25:08,  4.08s/it]
  2%|█▌                                                                                | 7/376 [00:28<25:23,  4.13s/it]
  2%|█▋                                                                                | 8/376 [00:33<25:46,  4.20s/it]
  2%|█▉                                                                                | 9/376 [00:38<25:54,  4.24s/it]
  3%|██▏                                                                              | 10/376 [00:43<26:27,  4.34s/it]
  3%|██▎                                                                              | 11/376 [00:48<26:33,  4.37s/it]
  3%|██▌                                                                              | 12/376 [00:52<26:38,  4.39s/it]
  3%|██▊                                                                              | 13/376 [00:57<26:58,  4.46s/it]
  4%|███                                                                              | 14/376 [01:01<26:38,  4.42s/it]
  4%|███▏                                                                             | 15/376 [01:06<26:45,  4.45s/it]
  4%|███▍                                                                             | 16/376 [01:11<26:46,  4.46s/it]
  5%|███▋                                                                             | 17/376 [01:14<26:06,  4.36s/it]
  5%|███▉                                                                             | 18/376 [01:18<26:08,  4.38s/it]
  5%|████                                                                             | 19/376 [01:23<26:11,  4.40s/it]
  5%|████▎                                                                            | 20/376 [01:27<25:52,  4.36s/it]
  6%|████▌                                                                            | 21/376 [01:31<25:40,  4.34s/it]
  6%|████▋                                                                            | 22/376 [01:35<25:40,  4.35s/it]
  6%|████▉                                                                            | 23/376 [01:40<25:35,  4.35s/it]
  6%|█████▏                                                                           | 24/376 [01:43<25:15,  4.31s/it]
  7%|█████▍                                                                           | 25/376 [01:48<25:18,  4.33s/it]
  7%|█████▌                                                                           | 26/376 [01:52<25:20,  4.35s/it]
  7%|█████▊                                                                           | 27/376 [01:56<24:59,  4.30s/it]
  7%|██████                                                                           | 28/376 [02:00<25:01,  4.31s/it]
  8%|██████▏                                                                          | 29/376 [02:05<25:02,  4.33s/it]
  8%|██████▍                                                                          | 30/376 [02:10<25:06,  4.36s/it]
  8%|██████▋                                                                          | 31/376 [02:14<24:54,  4.33s/it]
  9%|██████▉                                                                          | 32/376 [02:18<24:50,  4.33s/it]
  9%|███████                                                                          | 33/376 [02:24<24:58,  4.37s/it]
  9%|███████▎                                                                         | 34/376 [02:29<25:01,  4.39s/it]
  9%|███████▌                                                                         | 35/376 [02:34<25:02,  4.40s/it]
 10%|███████▊                                                                         | 36/376 [02:37<24:50,  4.38s/it]
 10%|███████▉                                                                         | 37/376 [02:41<24:43,  4.38s/it]
 10%|████████▏                                                                        | 38/376 [02:46<24:43,  4.39s/it]
 10%|████████▍                                                                        | 39/376 [02:51<24:43,  4.40s/it]
 11%|████████▌                                                                        | 40/376 [02:54<24:25,  4.36s/it]
 11%|████████▊                                                                        | 41/376 [02:59<24:26,  4.38s/it]
 11%|█████████                                                                        | 42/376 [03:04<24:26,  4.39s/it]
 11%|█████████▎                                                                       | 43/376 [03:09<24:27,  4.41s/it]
 12%|█████████▍                                                                       | 44/376 [03:13<24:16,  4.39s/it]
 12%|█████████▋                                                                       | 45/376 [03:17<24:11,  4.39s/it]
 12%|█████████▉                                                                       | 46/376 [03:22<24:10,  4.40s/it]
 12%|██████████▏                                                                      | 47/376 [03:27<24:09,  4.41s/it]
 13%|██████████▎                                                                      | 48/376 [03:30<23:55,  4.38s/it]
 13%|██████████▌                                                                      | 49/376 [03:35<23:55,  4.39s/it]
 13%|██████████▊                                                                      | 50/376 [03:40<23:56,  4.41s/it]
 14%|██████████▉                                                                      | 51/376 [03:45<23:55,  4.42s/it]
 14%|███████████▏                                                                     | 52/376 [03:50<23:54,  4.43s/it]
 14%|███████████▍                                                                     | 53/376 [03:53<23:41,  4.40s/it]
 14%|███████████▋                                                                     | 54/376 [03:58<23:40,  4.41s/it]
 15%|███████████▊                                                                     | 55/376 [04:04<23:45,  4.44s/it]
 15%|████████████                                                                     | 56/376 [04:10<23:51,  4.47s/it]
 15%|████████████▎                                                                    | 57/376 [04:16<23:55,  4.50s/it]
 15%|████████████▍                                                                    | 58/376 [04:21<23:53,  4.51s/it]
 16%|████████████▋                                                                    | 59/376 [04:26<23:51,  4.51s/it]
 16%|████████████▉                                                                    | 60/376 [04:30<23:44,  4.51s/it]
 16%|█████████████▏                                                                   | 61/376 [04:34<23:38,  4.50s/it]
 16%|█████████████▎                                                                   | 62/376 [04:40<23:38,  4.52s/it]
 17%|█████████████▌                                                                   | 63/376 [04:45<23:37,  4.53s/it]
 17%|█████████████▊                                                                   | 64/376 [04:50<23:34,  4.54s/it]
 17%|██████████████                                                                   | 65/376 [04:53<23:23,  4.51s/it]
 18%|██████████████▏                                                                  | 66/376 [04:58<23:21,  4.52s/it]
 18%|██████████████▍                                                                  | 67/376 [05:03<23:18,  4.53s/it]
 18%|██████████████▋                                                                  | 68/376 [05:08<23:16,  4.53s/it]
 18%|██████████████▊                                                                  | 69/376 [05:11<23:07,  4.52s/it]
 19%|███████████████                                                                  | 70/376 [05:16<23:04,  4.52s/it]
 19%|███████████████▎                                                                 | 71/376 [05:21<23:01,  4.53s/it]
 19%|███████████████▌                                                                 | 72/376 [05:26<22:58,  4.53s/it]
 19%|███████████████▋                                                                 | 73/376 [05:31<22:54,  4.54s/it]
 20%|███████████████▉                                                                 | 74/376 [05:34<22:44,  4.52s/it]
 20%|████████████████▏                                                                | 75/376 [05:39<22:44,  4.53s/it]
 20%|████████████████▎                                                                | 76/376 [05:45<22:43,  4.54s/it]
 20%|████████████████▌                                                                | 77/376 [05:50<22:40,  4.55s/it]
 21%|████████████████▊                                                                | 78/376 [05:54<22:36,  4.55s/it]
 21%|█████████████████                                                                | 79/376 [05:58<22:26,  4.53s/it]
 21%|█████████████████▏                                                               | 80/376 [06:03<22:24,  4.54s/it]
 22%|█████████████████▍                                                               | 81/376 [06:07<22:18,  4.54s/it]
 22%|█████████████████▋                                                               | 82/376 [06:10<22:07,  4.51s/it]
 22%|█████████████████▉                                                               | 83/376 [06:14<22:02,  4.51s/it]
 22%|██████████████████                                                               | 84/376 [06:19<21:57,  4.51s/it]
 23%|██████████████████▎                                                              | 85/376 [06:21<21:46,  4.49s/it]
 23%|██████████████████▌                                                              | 86/376 [06:25<21:41,  4.49s/it]
 23%|██████████████████▋                                                              | 87/376 [06:29<21:32,  4.47s/it]
 23%|██████████████████▉                                                              | 88/376 [06:32<21:25,  4.46s/it]
 24%|███████████████████▏                                                             | 89/376 [06:37<21:20,  4.46s/it]
 24%|███████████████████▍                                                             | 90/376 [06:39<21:09,  4.44s/it]
 24%|███████████████████▌                                                             | 91/376 [06:44<21:05,  4.44s/it]
 24%|███████████████████▊                                                             | 92/376 [06:48<21:00,  4.44s/it]
 25%|████████████████████                                                             | 93/376 [06:51<20:51,  4.42s/it]
 25%|████████████████████▎                                                            | 94/376 [06:55<20:46,  4.42s/it]
 25%|████████████████████▍                                                            | 95/376 [06:58<20:36,  4.40s/it]
 26%|████████████████████▋                                                            | 96/376 [07:02<20:31,  4.40s/it]
 26%|████████████████████▉                                                            | 97/376 [07:06<20:27,  4.40s/it]
 26%|█████████████████████                                                            | 98/376 [07:09<20:17,  4.38s/it]
 26%|█████████████████████▎                                                           | 99/376 [07:14<20:15,  4.39s/it]
 27%|█████████████████████▎                                                          | 100/376 [07:19<20:13,  4.40s/it]
 27%|█████████████████████▍                                                          | 101/376 [07:26<20:15,  4.42s/it]
 27%|█████████████████████▋                                                          | 102/376 [07:31<20:12,  4.43s/it]
 27%|█████████████████████▉                                                          | 103/376 [07:35<20:08,  4.43s/it]
 28%|██████████████████████▏                                                         | 104/376 [07:39<20:01,  4.42s/it]
 28%|██████████████████████▎                                                         | 105/376 [07:44<19:57,  4.42s/it]
 28%|██████████████████████▌                                                         | 106/376 [07:49<19:54,  4.42s/it]
 28%|██████████████████████▊                                                         | 107/376 [07:52<19:47,  4.41s/it]
 29%|██████████████████████▉                                                         | 108/376 [07:56<19:42,  4.41s/it]
 29%|███████████████████████▏                                                        | 109/376 [08:01<19:39,  4.42s/it]
 29%|███████████████████████▍                                                        | 110/376 [08:06<19:35,  4.42s/it]
 30%|███████████████████████▌                                                        | 111/376 [08:09<19:27,  4.41s/it]
 30%|███████████████████████▊                                                        | 112/376 [08:14<19:24,  4.41s/it]
 30%|████████████████████████                                                        | 113/376 [08:20<19:24,  4.43s/it]
 30%|████████████████████████▎                                                       | 114/376 [08:25<19:21,  4.43s/it]
 31%|████████████████████████▍                                                       | 115/376 [08:30<19:18,  4.44s/it]
 31%|████████████████████████▋                                                       | 116/376 [08:33<19:10,  4.42s/it]
 31%|████████████████████████▉                                                       | 117/376 [08:38<19:07,  4.43s/it]
 31%|█████████████████████████                                                       | 118/376 [08:42<19:03,  4.43s/it]
 32%|█████████████████████████▎                                                      | 119/376 [08:45<18:55,  4.42s/it]
 32%|█████████████████████████▌                                                      | 120/376 [08:50<18:51,  4.42s/it]
 32%|█████████████████████████▋                                                      | 121/376 [08:55<18:48,  4.42s/it]
 32%|█████████████████████████▉                                                      | 122/376 [09:00<18:44,  4.43s/it]
 33%|██████████████████████████▏                                                     | 123/376 [09:03<18:37,  4.42s/it]
 33%|██████████████████████████▍                                                     | 124/376 [09:07<18:33,  4.42s/it]
 33%|██████████████████████████▌                                                     | 125/376 [09:12<18:29,  4.42s/it]
 34%|██████████████████████████▊                                                     | 126/376 [09:17<18:26,  4.43s/it]
 34%|███████████████████████████                                                     | 127/376 [09:20<18:19,  4.41s/it]
 34%|███████████████████████████▏                                                    | 128/376 [09:25<18:15,  4.42s/it]
 34%|███████████████████████████▍                                                    | 129/376 [09:29<18:11,  4.42s/it]
 35%|███████████████████████████▋                                                    | 130/376 [09:32<18:04,  4.41s/it]
 35%|███████████████████████████▊                                                    | 131/376 [09:38<18:01,  4.41s/it]
 35%|████████████████████████████                                                    | 132/376 [09:42<17:57,  4.42s/it]
 35%|████████████████████████████▎                                                   | 133/376 [09:47<17:52,  4.41s/it]
 36%|████████████████████████████▌                                                   | 134/376 [09:50<17:46,  4.41s/it]
 36%|████████████████████████████▋                                                   | 135/376 [09:56<17:44,  4.42s/it]
 36%|████████████████████████████▉                                                   | 136/376 [10:01<17:41,  4.42s/it]
 36%|█████████████████████████████▏                                                  | 137/376 [10:06<17:37,  4.43s/it]
 37%|█████████████████████████████▎                                                  | 138/376 [10:11<17:33,  4.43s/it]
 37%|█████████████████████████████▌                                                  | 139/376 [10:13<17:26,  4.42s/it]
 37%|█████████████████████████████▊                                                  | 140/376 [10:18<17:23,  4.42s/it]
 38%|██████████████████████████████                                                  | 141/376 [10:24<17:20,  4.43s/it]
 38%|██████████████████████████████▏                                                 | 142/376 [10:30<17:18,  4.44s/it]
 38%|██████████████████████████████▍                                                 | 143/376 [10:36<17:16,  4.45s/it]
 38%|██████████████████████████████▋                                                 | 144/376 [10:40<17:12,  4.45s/it]
 39%|██████████████████████████████▊                                                 | 145/376 [10:43<17:05,  4.44s/it]
 39%|███████████████████████████████                                                 | 146/376 [10:48<17:02,  4.44s/it]
 39%|███████████████████████████████▎                                                | 147/376 [10:53<16:58,  4.45s/it]
 39%|███████████████████████████████▍                                                | 148/376 [10:58<16:54,  4.45s/it]
 40%|███████████████████████████████▋                                                | 149/376 [11:01<16:48,  4.44s/it]
 40%|███████████████████████████████▉                                                | 150/376 [11:06<16:44,  4.45s/it]
 40%|████████████████████████████████▏                                               | 151/376 [11:11<16:41,  4.45s/it]
 40%|████████████████████████████████▎                                               | 152/376 [11:16<16:37,  4.45s/it]
 41%|████████████████████████████████▌                                               | 153/376 [11:23<16:36,  4.47s/it]
 41%|████████████████████████████████▊                                               | 154/376 [11:29<16:33,  4.47s/it]
 41%|████████████████████████████████▉                                               | 155/376 [11:32<16:27,  4.47s/it]
 41%|█████████████████████████████████▏                                              | 156/376 [11:37<16:23,  4.47s/it]
 42%|█████████████████████████████████▍                                              | 157/376 [11:41<16:19,  4.47s/it]
 42%|█████████████████████████████████▌                                              | 158/376 [11:46<16:15,  4.47s/it]
 42%|█████████████████████████████████▊                                              | 159/376 [11:49<16:08,  4.47s/it]
 43%|██████████████████████████████████                                              | 160/376 [11:54<16:04,  4.47s/it]
 43%|██████████████████████████████████▎                                             | 161/376 [11:59<16:00,  4.47s/it]
 43%|██████████████████████████████████▍                                             | 162/376 [12:05<15:57,  4.48s/it]
 43%|██████████████████████████████████▋                                             | 163/376 [12:10<15:54,  4.48s/it]
 44%|██████████████████████████████████▉                                             | 164/376 [12:13<15:48,  4.47s/it]
 44%|███████████████████████████████████                                             | 165/376 [12:18<15:43,  4.47s/it]
 44%|███████████████████████████████████▎                                            | 166/376 [12:23<15:40,  4.48s/it]
 44%|███████████████████████████████████▌                                            | 167/376 [12:28<15:36,  4.48s/it]
 45%|███████████████████████████████████▋                                            | 168/376 [12:31<15:30,  4.48s/it]
 45%|███████████████████████████████████▉                                            | 169/376 [12:36<15:26,  4.48s/it]
 45%|████████████████████████████████████▏                                           | 170/376 [12:41<15:22,  4.48s/it]
 45%|████████████████████████████████████▍                                           | 171/376 [12:46<15:18,  4.48s/it]
 46%|████████████████████████████████████▌                                           | 172/376 [12:49<15:13,  4.48s/it]
 46%|████████████████████████████████████▊                                           | 173/376 [12:54<15:08,  4.48s/it]
 46%|█████████████████████████████████████                                           | 174/376 [12:59<15:04,  4.48s/it]
 47%|█████████████████████████████████████▏                                          | 175/376 [13:04<15:00,  4.48s/it]
 47%|█████████████████████████████████████▍                                          | 176/376 [13:08<14:56,  4.48s/it]
 47%|█████████████████████████████████████▋                                          | 177/376 [13:12<14:50,  4.48s/it]
 47%|█████████████████████████████████████▊                                          | 178/376 [13:17<14:47,  4.48s/it]
 48%|██████████████████████████████████████                                          | 179/376 [13:22<14:43,  4.48s/it]
 48%|██████████████████████████████████████▎                                         | 180/376 [13:26<14:38,  4.48s/it]
 48%|██████████████████████████████████████▌                                         | 181/376 [13:30<14:33,  4.48s/it]
 48%|██████████████████████████████████████▋                                         | 182/376 [13:39<14:33,  4.50s/it]
 49%|██████████████████████████████████████▉                                         | 183/376 [13:45<14:30,  4.51s/it]
 49%|███████████████████████████████████████▏                                        | 184/376 [13:48<14:24,  4.50s/it]
 49%|███████████████████████████████████████▎                                        | 185/376 [13:53<14:20,  4.50s/it]
 49%|███████████████████████████████████████▌                                        | 186/376 [13:58<14:16,  4.51s/it]
 50%|███████████████████████████████████████▊                                        | 187/376 [14:03<14:12,  4.51s/it]
 50%|████████████████████████████████████████                                        | 188/376 [14:08<14:08,  4.51s/it]
 50%|████████████████████████████████████████▏                                       | 189/376 [14:11<14:02,  4.50s/it]
 51%|████████████████████████████████████████▍                                       | 190/376 [14:16<13:58,  4.51s/it]
 51%|████████████████████████████████████████▋                                       | 191/376 [14:21<13:54,  4.51s/it]
 51%|████████████████████████████████████████▊                                       | 192/376 [14:27<13:51,  4.52s/it]
 51%|█████████████████████████████████████████                                       | 193/376 [14:34<13:48,  4.53s/it]
 52%|█████████████████████████████████████████▎                                      | 194/376 [14:40<13:45,  4.54s/it]
 52%|█████████████████████████████████████████▍                                      | 195/376 [14:47<13:43,  4.55s/it]
 52%|█████████████████████████████████████████▋                                      | 196/376 [14:52<13:39,  4.55s/it]
 52%|█████████████████████████████████████████▉                                      | 197/376 [15:00<13:38,  4.57s/it]
 53%|██████████████████████████████████████████▏                                     | 198/376 [15:08<13:36,  4.59s/it]
 53%|██████████████████████████████████████████▎                                     | 199/376 [15:11<13:30,  4.58s/it]
 53%|██████████████████████████████████████████▌                                     | 200/376 [15:17<13:27,  4.59s/it]
 53%|██████████████████████████████████████████▊                                     | 201/376 [15:22<13:22,  4.59s/it]
 54%|██████████████████████████████████████████▉                                     | 202/376 [15:27<13:18,  4.59s/it]
 54%|███████████████████████████████████████████▏                                    | 203/376 [15:32<13:14,  4.59s/it]
 54%|███████████████████████████████████████████▍                                    | 204/376 [15:35<13:08,  4.59s/it]
 55%|███████████████████████████████████████████▌                                    | 205/376 [15:40<13:04,  4.59s/it]
 55%|███████████████████████████████████████████▊                                    | 206/376 [15:45<13:00,  4.59s/it]
 55%|████████████████████████████████████████████                                    | 207/376 [15:50<12:55,  4.59s/it]
 55%|████████████████████████████████████████████▎                                   | 208/376 [15:55<12:51,  4.59s/it]
 56%|████████████████████████████████████████████▍                                   | 209/376 [15:58<12:46,  4.59s/it]
 56%|████████████████████████████████████████████▋                                   | 210/376 [16:03<12:41,  4.59s/it]
 56%|████████████████████████████████████████████▉                                   | 211/376 [16:09<12:37,  4.59s/it]
 56%|█████████████████████████████████████████████                                   | 212/376 [16:14<12:34,  4.60s/it]
 57%|█████████████████████████████████████████████▎                                  | 213/376 [16:20<12:29,  4.60s/it]
 57%|█████████████████████████████████████████████▌                                  | 214/376 [16:24<12:25,  4.60s/it]
 57%|█████████████████████████████████████████████▋                                  | 215/376 [16:29<12:20,  4.60s/it]
 57%|█████████████████████████████████████████████▉                                  | 216/376 [16:34<12:16,  4.60s/it]
 58%|██████████████████████████████████████████████▏                                 | 217/376 [16:39<12:12,  4.61s/it]
 58%|██████████████████████████████████████████████▍                                 | 218/376 [16:46<12:09,  4.62s/it]
 58%|██████████████████████████████████████████████▌                                 | 219/376 [16:54<12:07,  4.63s/it]
 59%|██████████████████████████████████████████████▊                                 | 220/376 [17:00<12:03,  4.64s/it]
 59%|███████████████████████████████████████████████                                 | 221/376 [17:04<11:58,  4.64s/it]
 59%|███████████████████████████████████████████████▏                                | 222/376 [17:10<11:54,  4.64s/it]
 59%|███████████████████████████████████████████████▍                                | 223/376 [17:15<11:50,  4.64s/it]
 60%|███████████████████████████████████████████████▋                                | 224/376 [17:21<11:46,  4.65s/it]
 60%|███████████████████████████████████████████████▊                                | 225/376 [17:26<11:42,  4.65s/it]
 60%|████████████████████████████████████████████████                                | 226/376 [17:31<11:38,  4.65s/it]
 60%|████████████████████████████████████████████████▎                               | 227/376 [17:37<11:33,  4.66s/it]
 61%|████████████████████████████████████████████████▌                               | 228/376 [17:41<11:29,  4.66s/it]
 61%|████████████████████████████████████████████████▋                               | 229/376 [17:45<11:24,  4.65s/it]
 61%|████████████████████████████████████████████████▉                               | 230/376 [17:51<11:20,  4.66s/it]
 61%|█████████████████████████████████████████████████▏                              | 231/376 [17:56<11:16,  4.66s/it]
 62%|█████████████████████████████████████████████████▎                              | 232/376 [18:02<11:11,  4.66s/it]
 62%|█████████████████████████████████████████████████▌                              | 233/376 [18:07<11:07,  4.67s/it]
 62%|█████████████████████████████████████████████████▊                              | 234/376 [18:13<11:03,  4.67s/it]
 62%|██████████████████████████████████████████████████                              | 235/376 [18:18<10:59,  4.68s/it]
 63%|██████████████████████████████████████████████████▏                             | 236/376 [18:23<10:54,  4.67s/it]
 63%|██████████████████████████████████████████████████▍                             | 237/376 [18:27<10:49,  4.67s/it]
 63%|██████████████████████████████████████████████████▋                             | 238/376 [18:32<10:45,  4.68s/it]
 64%|██████████████████████████████████████████████████▊                             | 239/376 [18:37<10:40,  4.68s/it]
 64%|███████████████████████████████████████████████████                             | 240/376 [18:43<10:36,  4.68s/it]
 64%|███████████████████████████████████████████████████▎                            | 241/376 [18:47<10:31,  4.68s/it]
 64%|███████████████████████████████████████████████████▍                            | 242/376 [18:51<10:26,  4.68s/it]
 65%|███████████████████████████████████████████████████▋                            | 243/376 [18:56<10:22,  4.68s/it]
 65%|███████████████████████████████████████████████████▉                            | 244/376 [19:02<10:17,  4.68s/it]
 65%|████████████████████████████████████████████████████▏                           | 245/376 [19:07<10:13,  4.68s/it]
 65%|████████████████████████████████████████████████████▎                           | 246/376 [19:12<10:09,  4.69s/it]
 66%|████████████████████████████████████████████████████▌                           | 247/376 [19:16<10:03,  4.68s/it]
 66%|████████████████████████████████████████████████████▊                           | 248/376 [19:20<09:59,  4.68s/it]
 66%|████████████████████████████████████████████████████▉                           | 249/376 [19:25<09:54,  4.68s/it]
 66%|█████████████████████████████████████████████████████▏                          | 250/376 [19:30<09:50,  4.68s/it]
 67%|█████████████████████████████████████████████████████▍                          | 251/376 [19:35<09:45,  4.68s/it]
 67%|█████████████████████████████████████████████████████▌                          | 252/376 [19:38<09:40,  4.68s/it]
 67%|█████████████████████████████████████████████████████▊                          | 253/376 [19:44<09:35,  4.68s/it]
 68%|██████████████████████████████████████████████████████                          | 254/376 [19:49<09:31,  4.68s/it]
 68%|██████████████████████████████████████████████████████▎                         | 255/376 [19:55<09:27,  4.69s/it]
 68%|██████████████████████████████████████████████████████▍                         | 256/376 [20:02<09:23,  4.70s/it]
 68%|██████████████████████████████████████████████████████▋                         | 257/376 [20:08<09:19,  4.70s/it]
 69%|██████████████████████████████████████████████████████▉                         | 258/376 [20:13<09:15,  4.70s/it]
 69%|███████████████████████████████████████████████████████                         | 259/376 [20:19<09:10,  4.71s/it]
 69%|███████████████████████████████████████████████████████▎                        | 260/376 [20:24<09:06,  4.71s/it]
 69%|███████████████████████████████████████████████████████▌                        | 261/376 [20:29<09:01,  4.71s/it]
 70%|███████████████████████████████████████████████████████▋                        | 262/376 [20:32<08:56,  4.71s/it]
 70%|███████████████████████████████████████████████████████▉                        | 263/376 [20:38<08:52,  4.71s/it]
 70%|████████████████████████████████████████████████████████▏                       | 264/376 [20:43<08:47,  4.71s/it]
 70%|████████████████████████████████████████████████████████▍                       | 265/376 [20:48<08:43,  4.71s/it]
 71%|████████████████████████████████████████████████████████▌                       | 266/376 [20:54<08:38,  4.71s/it]
 71%|████████████████████████████████████████████████████████▊                       | 267/376 [20:59<08:34,  4.72s/it]
 71%|█████████████████████████████████████████████████████████                       | 268/376 [21:02<08:28,  4.71s/it]
 72%|█████████████████████████████████████████████████████████▏                      | 269/376 [21:07<08:24,  4.71s/it]
 72%|█████████████████████████████████████████████████████████▍                      | 270/376 [21:13<08:19,  4.72s/it]
 72%|█████████████████████████████████████████████████████████▋                      | 271/376 [21:18<08:15,  4.72s/it]
 72%|█████████████████████████████████████████████████████████▊                      | 272/376 [21:24<08:11,  4.72s/it]
 73%|██████████████████████████████████████████████████████████                      | 273/376 [21:29<08:06,  4.72s/it]
 73%|██████████████████████████████████████████████████████████▎                     | 274/376 [21:35<08:02,  4.73s/it]
 73%|██████████████████████████████████████████████████████████▌                     | 275/376 [21:40<07:57,  4.73s/it]
 73%|██████████████████████████████████████████████████████████▋                     | 276/376 [21:43<07:52,  4.72s/it]
 74%|██████████████████████████████████████████████████████████▉                     | 277/376 [21:49<07:47,  4.73s/it]
 74%|███████████████████████████████████████████████████████████▏                    | 278/376 [21:54<07:43,  4.73s/it]
 74%|███████████████████████████████████████████████████████████▎                    | 279/376 [21:59<07:38,  4.73s/it]
 74%|███████████████████████████████████████████████████████████▌                    | 280/376 [22:03<07:33,  4.73s/it]
 75%|███████████████████████████████████████████████████████████▊                    | 281/376 [22:07<07:28,  4.72s/it]
 75%|████████████████████████████████████████████████████████████                    | 282/376 [22:12<07:24,  4.73s/it]
 75%|████████████████████████████████████████████████████████████▏                   | 283/376 [22:17<07:19,  4.73s/it]
 76%|████████████████████████████████████████████████████████████▍                   | 284/376 [22:22<07:14,  4.73s/it]
 76%|████████████████████████████████████████████████████████████▋                   | 285/376 [22:29<07:10,  4.73s/it]
 76%|████████████████████████████████████████████████████████████▊                   | 286/376 [22:34<07:06,  4.74s/it]
 76%|█████████████████████████████████████████████████████████████                   | 287/376 [22:37<07:01,  4.73s/it]
 77%|█████████████████████████████████████████████████████████████▎                  | 288/376 [22:42<06:56,  4.73s/it]
 77%|█████████████████████████████████████████████████████████████▍                  | 289/376 [22:47<06:51,  4.73s/it]
 77%|█████████████████████████████████████████████████████████████▋                  | 290/376 [22:52<06:47,  4.73s/it]
 77%|█████████████████████████████████████████████████████████████▉                  | 291/376 [22:55<06:41,  4.73s/it]
 78%|██████████████████████████████████████████████████████████████▏                 | 292/376 [23:00<06:37,  4.73s/it]
 78%|██████████████████████████████████████████████████████████████▎                 | 293/376 [23:06<06:32,  4.73s/it]
 78%|██████████████████████████████████████████████████████████████▌                 | 294/376 [23:12<06:28,  4.74s/it]
 78%|██████████████████████████████████████████████████████████████▊                 | 295/376 [23:19<06:24,  4.74s/it]
 79%|██████████████████████████████████████████████████████████████▉                 | 296/376 [23:24<06:19,  4.74s/it]
 79%|███████████████████████████████████████████████████████████████▏                | 297/376 [23:29<06:14,  4.74s/it]
 79%|███████████████████████████████████████████████████████████████▍                | 298/376 [23:33<06:10,  4.74s/it]
 80%|███████████████████████████████████████████████████████████████▌                | 299/376 [23:37<06:04,  4.74s/it]
 80%|███████████████████████████████████████████████████████████████▊                | 300/376 [23:41<06:00,  4.74s/it]
 80%|████████████████████████████████████████████████████████████████                | 301/376 [23:46<05:55,  4.74s/it]
 80%|████████████████████████████████████████████████████████████████▎               | 302/376 [23:50<05:50,  4.74s/it]
 81%|████████████████████████████████████████████████████████████████▍               | 303/376 [23:54<05:45,  4.73s/it]
 81%|████████████████████████████████████████████████████████████████▋               | 304/376 [23:58<05:40,  4.73s/it]
 81%|████████████████████████████████████████████████████████████████▉               | 305/376 [24:01<05:35,  4.73s/it]
 81%|█████████████████████████████████████████████████████████████████               | 306/376 [24:06<05:30,  4.73s/it]
 82%|█████████████████████████████████████████████████████████████████▎              | 307/376 [24:12<05:26,  4.73s/it]
 82%|█████████████████████████████████████████████████████████████████▌              | 308/376 [24:16<05:21,  4.73s/it]
 82%|█████████████████████████████████████████████████████████████████▋              | 309/376 [24:21<05:16,  4.73s/it]
 82%|█████████████████████████████████████████████████████████████████▉              | 310/376 [24:24<05:11,  4.72s/it]
 83%|██████████████████████████████████████████████████████████████████▏             | 311/376 [24:29<05:07,  4.72s/it]
 83%|██████████████████████████████████████████████████████████████████▍             | 312/376 [24:33<05:02,  4.72s/it]
 83%|██████████████████████████████████████████████████████████████████▌             | 313/376 [24:36<04:57,  4.72s/it]
 84%|██████████████████████████████████████████████████████████████████▊             | 314/376 [24:41<04:52,  4.72s/it]
 84%|███████████████████████████████████████████████████████████████████             | 315/376 [24:46<04:47,  4.72s/it]
 84%|███████████████████████████████████████████████████████████████████▏            | 316/376 [24:48<04:42,  4.71s/it]
 84%|███████████████████████████████████████████████████████████████████▍            | 317/376 [24:53<04:37,  4.71s/it]
 85%|███████████████████████████████████████████████████████████████████▋            | 318/376 [24:57<04:33,  4.71s/it]
 85%|███████████████████████████████████████████████████████████████████▊            | 319/376 [25:00<04:28,  4.70s/it]
 85%|████████████████████████████████████████████████████████████████████            | 320/376 [25:05<04:23,  4.70s/it]
 85%|████████████████████████████████████████████████████████████████████▎           | 321/376 [25:10<04:18,  4.70s/it]
 86%|████████████████████████████████████████████████████████████████████▌           | 322/376 [25:14<04:14,  4.70s/it]
 86%|████████████████████████████████████████████████████████████████████▋           | 323/376 [25:17<04:09,  4.70s/it]
 86%|████████████████████████████████████████████████████████████████████▉           | 324/376 [25:22<04:04,  4.70s/it]
 86%|█████████████████████████████████████████████████████████████████████▏          | 325/376 [25:27<03:59,  4.70s/it]
 87%|█████████████████████████████████████████████████████████████████████▎          | 326/376 [25:30<03:54,  4.69s/it]
 87%|█████████████████████████████████████████████████████████████████████▌          | 327/376 [25:34<03:50,  4.69s/it]
 87%|█████████████████████████████████████████████████████████████████████▊          | 328/376 [25:39<03:45,  4.69s/it]
 88%|██████████████████████████████████████████████████████████████████████          | 329/376 [25:43<03:40,  4.69s/it]
 88%|██████████████████████████████████████████████████████████████████████▏         | 330/376 [25:46<03:35,  4.69s/it]
 88%|██████████████████████████████████████████████████████████████████████▍         | 331/376 [25:51<03:30,  4.69s/it]
 88%|██████████████████████████████████████████████████████████████████████▋         | 332/376 [25:54<03:26,  4.68s/it]
 89%|██████████████████████████████████████████████████████████████████████▊         | 333/376 [25:59<03:21,  4.68s/it]
 89%|███████████████████████████████████████████████████████████████████████         | 334/376 [26:03<03:16,  4.68s/it]
 89%|███████████████████████████████████████████████████████████████████████▎        | 335/376 [26:08<03:11,  4.68s/it]
 89%|███████████████████████████████████████████████████████████████████████▍        | 336/376 [26:11<03:07,  4.68s/it]
 90%|███████████████████████████████████████████████████████████████████████▋        | 337/376 [26:20<03:02,  4.69s/it]
 90%|███████████████████████████████████████████████████████████████████████▉        | 338/376 [26:27<02:58,  4.70s/it]
 90%|████████████████████████████████████████████████████████████████████████▏       | 339/376 [26:32<02:53,  4.70s/it]
 90%|████████████████████████████████████████████████████████████████████████▎       | 340/376 [26:34<02:48,  4.69s/it]
 91%|████████████████████████████████████████████████████████████████████████▌       | 341/376 [26:39<02:44,  4.69s/it]
 91%|████████████████████████████████████████████████████████████████████████▊       | 342/376 [26:44<02:39,  4.69s/it]
 91%|████████████████████████████████████████████████████████████████████████▉       | 343/376 [26:48<02:34,  4.69s/it]
 91%|█████████████████████████████████████████████████████████████████████████▏      | 344/376 [26:52<02:30,  4.69s/it]
 92%|█████████████████████████████████████████████████████████████████████████▍      | 345/376 [26:57<02:25,  4.69s/it]
 92%|█████████████████████████████████████████████████████████████████████████▌      | 346/376 [27:02<02:20,  4.69s/it]
 92%|█████████████████████████████████████████████████████████████████████████▊      | 347/376 [27:07<02:16,  4.69s/it]
 93%|██████████████████████████████████████████████████████████████████████████      | 348/376 [27:10<02:11,  4.69s/it]
 93%|██████████████████████████████████████████████████████████████████████████▎     | 349/376 [27:15<02:06,  4.69s/it]
 93%|██████████████████████████████████████████████████████████████████████████▍     | 350/376 [27:20<02:01,  4.69s/it]
 93%|██████████████████████████████████████████████████████████████████████████▋     | 351/376 [27:23<01:57,  4.68s/it]
 94%|██████████████████████████████████████████████████████████████████████████▉     | 352/376 [27:28<01:52,  4.68s/it]
 94%|███████████████████████████████████████████████████████████████████████████     | 353/376 [27:32<01:47,  4.68s/it]
 94%|███████████████████████████████████████████████████████████████████████████▎    | 354/376 [27:37<01:42,  4.68s/it]
 94%|███████████████████████████████████████████████████████████████████████████▌    | 355/376 [27:40<01:38,  4.68s/it]
 95%|███████████████████████████████████████████████████████████████████████████▋    | 356/376 [27:44<01:33,  4.68s/it]
 95%|███████████████████████████████████████████████████████████████████████████▉    | 357/376 [27:48<01:28,  4.67s/it]
 95%|████████████████████████████████████████████████████████████████████████████▏   | 358/376 [27:52<01:24,  4.67s/it]
 95%|████████████████████████████████████████████████████████████████████████████▍   | 359/376 [27:58<01:19,  4.68s/it]
 96%|████████████████████████████████████████████████████████████████████████████▌   | 360/376 [28:03<01:14,  4.68s/it]
 96%|████████████████████████████████████████████████████████████████████████████▊   | 361/376 [28:08<01:10,  4.68s/it]
 96%|█████████████████████████████████████████████████████████████████████████████   | 362/376 [28:11<01:05,  4.67s/it]
 97%|█████████████████████████████████████████████████████████████████████████████▏  | 363/376 [28:15<01:00,  4.67s/it]
 97%|█████████████████████████████████████████████████████████████████████████████▍  | 364/376 [28:20<00:56,  4.67s/it]
 97%|█████████████████████████████████████████████████████████████████████████████▋  | 365/376 [28:24<00:51,  4.67s/it]
 97%|█████████████████████████████████████████████████████████████████████████████▊  | 366/376 [28:28<00:46,  4.67s/it]
 98%|██████████████████████████████████████████████████████████████████████████████  | 367/376 [28:33<00:42,  4.67s/it]
 98%|██████████████████████████████████████████████████████████████████████████████▎ | 368/376 [28:37<00:37,  4.67s/it]
 98%|██████████████████████████████████████████████████████████████████████████████▌ | 369/376 [28:41<00:32,  4.67s/it]
 98%|██████████████████████████████████████████████████████████████████████████████▋ | 370/376 [28:45<00:27,  4.66s/it]
 99%|██████████████████████████████████████████████████████████████████████████████▉ | 371/376 [28:50<00:23,  4.66s/it]
 99%|███████████████████████████████████████████████████████████████████████████████▏| 372/376 [28:55<00:18,  4.67s/it]
 99%|███████████████████████████████████████████████████████████████████████████████▎| 373/376 [28:59<00:13,  4.66s/it]
 99%|███████████████████████████████████████████████████████████████████████████████▌| 374/376 [29:03<00:09,  4.66s/it]
100%|███████████████████████████████████████████████████████████████████████████████▊| 375/376 [29:08<00:04,  4.66s/it]
[MoviePy] Done.
[MoviePy] >>>> Video ready: harder_challenge_video_output.mp4 

Wall time: 29min 19s